/*
 *  Copyright 2011 yura.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.opu.vcforms.transform_t2d.util;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 *
 * @author yura
 */
public class DocxUtil extends XmlUtil {

    /**
     * Return the set of the name of files that represent document part as a
     * part of it text contant.
     * @param archive all data
     * @return the names of entry that need to parse
     * @throws IllegalArgumentException if it is not a docx archive
     */
    public Set<String> documents(Map<String, byte[]> archive)
            throws IllegalArgumentException {

        if (!archive.containsKey("word/document.xml")) {
            throw new IllegalArgumentException("Archive don't containe 'word/document.xml'");
        }

        return new HashSet<String>(Arrays.asList("word/document.xml"));
    }

    public byte[] convertOOXMLToDataXML(byte[] in) throws SAXException,
            IllegalArgumentException {

        SaxOOXMLToDataXML handler = new SaxOOXMLToDataXML();

        try {
            handler.openDocument();
            parse(in, handler);
            handler.closeDocument();
        } catch (Exception ex) {
            throw new IllegalArgumentException("Bad 'word/document.xml'", ex);
        }

        // Return data
        return handler.accumulator.toString().getBytes();
    }

    class SaxOOXMLToDataXML extends DefaultHandler {

        public static final String TAG_URI = "w";
        public static final String CUSTOM_XML_TAG = "customXml";
        public static final String CUSTOM_XML_PR_TAG = "customXmlPr";
        public static final String SMART_TAG_TAG = "smartTag";
        public static final String SMART_TAG_PR_TAG = "smartTagPr";
        public static final String ATTR_TAG = "attr";

        public static final String URI_TAG = "uri";

        public static final int ANOTHER = 0;
        public static final int CUSTOM_XML = 1;
        public static final int CUSTOM_XML_PR = 2;
        public static final int SMART_TAG = 3;
        public static final int SMART_TAG_PR = 4;
        public static final int ATTR = 5;
        public static final int TEXT = 6;

        // <editor-fold defaultstate="collapsed" desc="Constructors">
        private Stack<Integer> stack = new Stack<Integer>();
        private boolean inVar = false;
        private boolean inVarTxt = false;
        private boolean inProp = false;
        private StringBuilder varText;
        private StringBuilder varProperties;
        StringBuilder accumulator = new StringBuilder();

        public SaxOOXMLToDataXML() {
            stack.push(0);
        }// </editor-fold>

        ////////////////////////////////////////////////////////////////////
        // Handler methods.
        ////////////////////////////////////////////////////////////////////
        // <editor-fold defaultstate="collapsed" desc="Handler methods">
        // When the parser encounters plain text (not XML elements), it calls
        // this method, which accumulates them in a string buffer
        @Override
        public void characters(char[] buffer, int start, int length) {
            openText();
            addText(buffer, start, length);
            closeText();
        }

        // Every time the parser encounters the beginning of a new element, it
        // calls this method, which resets the string buffer
        @Override
        public void startElement(String uri, String localName, String qName,
                Attributes atrbts) throws SAXException {

            int type = type(qName);
            final String tagUri = atrbts.getValue(TAG_URI, URI_TAG);

            switch(type){
                case 1: // customXml
                    // if not our URI
                    if (tagUri != null && !CUSTOM_XML_URI.equals(tagUri)){
                        type = 0;
                        appendTag(uri, localName, qName, atrbts);
                        break;
                    }
                    openContent();
                    break;
                case 2: // customXmlPr
                    openProperties();
                    break;
                case 3: // smartTag
                    // if not our URI
                    if (tagUri != null && !CUSTOM_XML_URI.equals(tagUri)){
                        type = 0;
                        appendTag(uri, localName, qName, atrbts);
                        break;
                    }
                    openVar();
                    break;
                case 4: // smartTagPr
                    openProperties();
                    break;
                case 5: // attr
                    addPtopety(atrbts);
                    break;
                case 6: // w:t
                    openVarText(uri, localName, qName, atrbts);
                    break;
                case 0:
                default:
                    appendTag(uri, localName, qName, atrbts);
                    break;
            }

            stack.push(type);
        }

        // When the parser encounters the end of an element, it calls this method
        @Override
        public void endElement(String uri, String localName, String qName)
                throws SAXException {
            int pop = stack.pop();

            switch(pop){
                case 1: // customXml
                    closeContent();
                    break;
                case 2: // customXmlPr
                    closeProperties();
                    break;
                case 3: // smartTag
                    closeVar();
                    break;
                case 4: // smartTagPr
                    closeProperties();
                    break;
                case 5: // attr
                    break;
                case 6: // w:t
                    closeVarText(uri, localName, qName);
                    break;
                case 0:
                default:
                    appendCloseTag(uri, localName, qName);
                    break;
            }
        }// </editor-fold>

        ////////////////////////////////////////////////////////////////////
        // document builder methods.
        ////////////////////////////////////////////////////////////////////
        // <editor-fold defaultstate="collapsed" desc="document builder methods">
        private void openDocument() {
            accumulator.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<template><document>");
            openText();
            append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n");
            closeText();
        }

        private void closeDocument() {
            accumulator.append("</document></template>");
        }

        private void openText() {
            if(openDataSection()) {
                accumulator.append("<data><text><![CDATA[");
            }
        }

        private void closeText() {
            if(openDataSection()) {
                accumulator.append("]]></text></data>");
            }
        }

        private void openContent() {
            accumulator.append("<data type=\"content\">");
        }

        private void closeContent() {
            accumulator.append("</data>");
        }

        private void openVar() {
            varText = new StringBuilder();
            varProperties = new StringBuilder();
            inVar = true;
        }

        private void closeVar() {
            inVar = false;
        }

        private void openVarText(String uri, String localName, String qName, Attributes atrbts) {
            appendTag(uri, localName, qName, atrbts);
            inVarTxt = true;
        }

        private void closeVarText(String uri, String localName, String qName) {
            accumulator.append("<data type=\"var\">").append(varProperties)
                    .append("<text><![CDATA[").append(varText).append("]]></text>")
                    .append("</data>");
            inVarTxt = false;
            appendCloseTag(uri, localName, qName);
        }

        private void openProperties() {
            if(inVar){
                varProperties.append("<properties>");
            } else {
                accumulator.append("<properties>");
            }

            inProp = true;
        }

        private void closeProperties() {
            if(inVar){
                varProperties.append("</properties>");
            } else {
                accumulator.append("</properties>");
            }

            inProp = false;
        }

        private void addPtopety(Attributes atrbts) {
            String str = "<property name = \"" + atrbts.getValue("w:name")
                     + "\"><![CDATA["+ atrbts.getValue("w:val") + "]]></property>";

            if(inVar){
                varProperties.append(str);
            } else {
                accumulator.append(str);
            }
        }

        @Override
        protected void append(CharSequence str){
            if(inVarTxt){
                final String trim = str.toString().trim();
                if (!trim.isEmpty()){
                    varText.append(trim);
                }
            } else if (inProp){
                // do nothing
            } else {
                accumulator.append(str);
            }
        }// </editor-fold>

        ////////////////////////////////////////////////////////////////////
        // Util methods.
        ////////////////////////////////////////////////////////////////////

        public int type(String name){
            final String[] arr = name.split("\\:", 2);
            if (TAG_URI.equals(arr[0]) && arr.length > 1){
                name = arr[1];
                if (CUSTOM_XML_TAG.equals(name)){
                    return 1;
                } else if (CUSTOM_XML_PR_TAG.equals(name)){
                    return 2;
                } else if (SMART_TAG_TAG.equals(name)){
                    return 3;
                } else if (SMART_TAG_PR_TAG.equals(name)){
                    return 4;
                } else if (ATTR_TAG.equals(name)){
                    return 5;
                } else if (inVar && TEXT_TAG.equals(name)){
                    return 6;
                } else {
                    return 0;
                }
            } else {
                System.err.println("FATAL " + name);
                return 0;
            }
        }

        public String type(int type, String defName){
            switch(type){
                case 1: // customXml
                    return CUSTOM_XML_TAG;
                case 2: // customXmlPr
                    return CUSTOM_XML_PR_TAG;
                case 3: // smartTag
                    return SMART_TAG_TAG;
                case 4: // smartTagPr
                    return SMART_TAG_PR_TAG;
                case 5: // attr
                    return ATTR_TAG;
                case 0:
                default:
                    return String.valueOf(defName);
            }
        }

        /**
         *  Retrun true if we not in properties section and we not in var w:t
         * section
         * @return true if we not in properties section and we not in var w:t
         * section
         */
        public boolean openDataSection() {
            return (!inVar || !inVarTxt) && !inProp;
        }

        public void appendTag(String uri, String localName, String qName, Attributes atrbts) {
            openText();
            openTag(uri, localName, qName, atrbts);
            closeText();
        }

        public void appendCloseTag(String uri, String localName, String qName) {
            openText();
            closeTag(uri, localName, qName);
            closeText();
        }
    }
}
